package com.gzist.elderlyserviceplatform.service;

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.ai.advisor.RetrievalRerankAdvisor;
import com.alibaba.cloud.ai.model.RerankModel;
import com.gzist.elderlyserviceplatform.entity.dto.ElderlyHealthAnalysisDTO;
import com.gzist.elderlyserviceplatform.entity.dto.WsMessage;
import com.gzist.elderlyserviceplatform.entity.model.ImageRequestDTO;
import com.gzist.elderlyserviceplatform.entity.model.Message;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.image.ImageOptionsBuilder;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.netty.http.client.PrematureCloseException;
import reactor.util.retry.Retry;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;

/**
 * AI业务实现类(SpringAI版)
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AiService {

    private final ChatModel chatModel;
    private final ImageModel imageModel;
    private final VectorStore vectorStore;
    private final RerankModel rerankModel;

    // 系统提示词模板（角色定义）
    private final static String SYSTEM_PROMPT = """
            你是一个基于检索增强生成(RAG)技术的养老平台领域智能助手。你的回答必须：

            1. 严格基于检索到的知识片段生成
            2. 禁止直接复制原文，需进行语义重构和专业改写
            3. 回答应客观、专业、完整，避免主观臆断
            4. 若知识片段不包含答案，明确回复"抱歉，当前信息无法回答该问题"
            5. 敏感信息处理规则：
               - 过滤任何个人身份信息(PII)
               - 拒绝回答涉及违法、伦理风险的问题
               - 避免提供医疗、法律等专业领域的具体建议

            """;

    // 用户提示词模板（重排序阶段）
    private final static String USER_PROMPT_RERANK = """
            以下是知识库检索信息：
            --------------------
            {question_answer_context}
            --------------------

            请基于上述信息，结合历史对话内容回答用户问题。回答应：
            1. 内容完整且紧扣问题核心
            2. 避免直接摘抄原文，需语义转化
            3. 语言简洁专业，逻辑清晰连贯
            4. 若信息不足请礼貌告知用户

            回答建议：先直接回应问题关键，再展开必要解释，确保用户获得准确且易于理解的信息。
            """;

    private static final String HEALTH_STRATEGY_PROMPT_TEMPLATE =
            """
                    作为颐联汇智慧养老健康管理专家，根据以下信息生成个性化健康方案：
                    ## 核心健康档案
                    - **姓名**： %s
                    - **年龄**： %d岁（%s代）
                    - **体征指标**：身高 %.1fcm | 体重 %.1fkg | BMI **%.1f** %s
                    - **血压记录**： %s
                    - **生活习惯**： %s%s

                    ## 多维健康建议
                    ### Ⅰ 日常习惯优化 [[2]](#__2)
                    1. **饮食管理**：
                       - 每日3种颜色蔬果（如番茄/菠菜/胡萝卜）
                       - 采用小餐盘控制食量（推荐直径18cm）

                    2. **睡眠改善**：
                       - 建立"15分钟渐进式"就寝仪式（关灯→冥想→入眠）
                       - 使用智能手环监测睡眠周期（深睡≥1.5小时为优）

                    ### Ⅱ 科学运动方案 [[3]](#__3) [[8]](#__8)
                    ```markdown
                    1. **有氧运动**：
                       - 每周5次快走（每次30分钟，配速5km/h）
                       - 水中漫步（每周2次，水温30℃最佳）

                    2. **力量训练**：
                       - 弹力带深蹲（3组×10次，组间休息90秒）
                       - 靠墙俯卧撑（每日2组×15次）

                    3. **平衡训练**：
                       - 单腿站立（扶椅背，每日3次×30秒）
                       - 脚跟行走（沿直线，每日2分钟）
                    ```

                    ### Ⅲ 社区活动推荐 [[7]](#__7)
                    - **八段锦晨练**
                      - 频次建议：每周3次
                      - 最佳时段：7:30 AM
                    - **健康膳食课**
                      - 频次建议：每月1次
                      - 最佳时段：每月第二个周四
                    - **园艺疗愈**
                      - 频次建议：每周1次
                      - 最佳时段：周五下午3点

                    ## 安全须知
                    %s
                    ✅ 运动时佩戴紧急呼叫设备（如智能手表SOS功能）
                    ✅ 饭后1小时再开始运动（避免胃肠不适）

                    ### 生成规则
                    - 数据可视化：用↑↓箭头表示趋势，🚩标记超标项
                    - 运动处方：参考《中国老年人体育健身活动指南》
                    - 资源匹配：对接国家卫健委"银龄健康工程"服务目录

                    ### 备注
                    本方案仅供参考，具体执行需结合个人实际情况并在专业医生指导下进行。""";


    /**
     * 处理聊天WebSocket连接，用于接收用户消息并返回相应的回答
     * 如果这是用户的新会话，会首先发送一个模板提示
     * 该方法使用React式编程模型，通过Flux发布者异步处理消息
     *
     * @param message 客户端传过来的消息体
     * @param userId  用户唯一标识符
     * @return 返回一个Flux发布者，它将异步地发送聊天响应
     */
    public Flux<String> handleChatWebSocket(String message, String userId) {
        // 处理用户的消息
        return chatStream(userId, message)
                .doOnNext(response -> log.info("发送响应: {}", response))
                .onErrorResume(e -> {
                    log.error("WebSocket通信错误: {}", e.getMessage());
                    return Flux.just("抱歉，服务器处理请求时发生错误，请稍后重试。");
                });
    }

    /**
     * 向指定用户发送聊天请求
     * 该方法负责构建聊天请求，发送给API，并处理响应
     *
     * @param userId  用户ID，用于从会话历史记录中获取该用户的聊天记录
     * @param message 客户端传过来的消息体
     * @return 返回一个Flux流，包含可能的聊天回复
     */
    private Flux<String> chatStream(String userId, String message) {
        WsMessage wsMessage;
        List<Message> frontMessageList;
        try {
            wsMessage = JSONUtil.toBean(message, WsMessage.class);
        } catch (Exception e) {
            log.error("解析消息时发生错误: {}", e.getMessage());
            return Flux.just("抱歉，服务器处理请求时发生错误，请检查消息格式是否正确。");
        }
        if (wsMessage == null) {
            return Flux.just("抱歉，服务器处理请求时发生错误，请稍后重试。");
        }
        frontMessageList = wsMessage.getMessageList() != null ?
                wsMessage.getMessageList() : new ArrayList<>();
        // 处理对话记录类型
        ArrayList<org.springframework.ai.chat.messages.Message> chatMessageList = new ArrayList<>();
        // 去掉assistant欢迎语即第一句话
        Message firstAssistantMessage = frontMessageList.get(0);
        if ("assistant".equals(firstAssistantMessage.getRole())) {
            frontMessageList.remove(0);
        }
        for (Message frontMessage : frontMessageList) {
            if (frontMessage.getRole().equals("user")) {
                chatMessageList.add(new UserMessage(frontMessage.getContent()));
            } else if (frontMessage.getRole().equals("assistant")) {
                chatMessageList.add(new AssistantMessage(frontMessage.getContent()));
            }
        }

        // 配置搜索参数
        SearchRequest searchRequest = SearchRequest.builder()
                .similarityThreshold(0.2)
                .topK(7)
                .build();
        // 配置检索顾问
        RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
                .queryTransformers()
                .documentRetriever(VectorStoreDocumentRetriever.builder()
                        .similarityThreshold(0.2)   // 设置相似度阈值
                        .topK(7)                    // 设置返回前7条相关结果
                        .vectorStore(vectorStore)   // 设置向量存储
                        .build())
                .order(1)                           // 指定执行顺序(先执行检索前处理与检索)
                .build();
        // 配置重排序顾问
        RetrievalRerankAdvisor retrievalRerankAdvisor = new RetrievalRerankAdvisor(
                vectorStore,
                rerankModel,
                searchRequest,
                // 创建带变量注入的模板文本(只能存在一个变量即最终的检索结果)
                USER_PROMPT_RERANK,
                0.6  // 重排序阈值
        );
        // 构建ChatClient（包含双阶段RAG处理）
        ChatClient chatClient = ChatClient.builder(chatModel)
                .defaultSystem(SYSTEM_PROMPT)  // 设置系统角色
                .defaultAdvisors(
                        // 基础检索顾问（使用第一阶段模板）
                        retrievalAugmentationAdvisor,
                        // 重排序顾问（使用优化后模板）
                        retrievalRerankAdvisor
                )
                .build();
        // 注意注意的一点是，不能添加options，否则tools的调用会失败
        return chatClient.prompt(new Prompt(chatMessageList))   // 设置聊天请求的提示词包括记忆
                .stream()                                       // 启用流式响应
                .content()                                      // 获取聊天响应的内容
                .timeout(Duration.ofSeconds(300))               // 设置超时时间
                .retryWhen(Retry.backoff(4, Duration.ofSeconds(2)) // 添加重试机制
                        .filter(throwable ->
                                throwable instanceof PrematureCloseException
                                        || throwable instanceof TimeoutException
                                        || throwable instanceof RuntimeException)
                        .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
                            log.error("重试次数已用完，最后一次错误: {}", retrySignal.failure().getMessage());
                            return new RuntimeException("服务暂时不可用，请稍后重试");
                        }))
                .onErrorResume(e -> {
                    log.error("请求处理错误: {}", e.getMessage());
                    // 返回纯文本错误信息，避免后续解析失败
                    return Flux.just("抱歉，服务器处理请求时发生错误: " + e.getMessage());
                })
                .map(response -> {                              // 处理响应内容即切片内容
                    try {
                        if ("[DONE]".equals(response)) {
                            return "";
                        }
                    } catch (Exception e) {
                        log.error("解析响应时出错: {}", e.getMessage());
                        log.error("原始响应: {}", response);
                        return "";
                    }
                    return response;
                }).filter(content -> content != null && !content.isEmpty() && !content.equals("null"));

    }

    /**
     * 获取完整的 AI 响应内容
     *
     * @param prompt 用户提示词
     * @return AI 模型的完整响应内容
     */
    public String chatString(String prompt) {
        ChatClient chatClient = ChatClient.builder(chatModel).build();
        return chatClient.prompt()
                .user(prompt)
                .call()
                .content();
    }

    /**
     * 根据老年人信息生成健康方案。
     * <p>
     * 该函数通过构建与健康方案生成服务的对话上下文，发送请求并获取生成的健康方案。
     *
     * @param dto 包含老年人基本信息的对象，如姓名、年龄、性别等。
     * @return 返回生成的健康方案文本。如果生成失败，则返回错误信息。
     */
    public String generateHealthPlan(ElderlyHealthAnalysisDTO dto) {
        // 参数预处理（防御性编程）
        final int age = Optional.ofNullable(dto.getAge()).orElse(0);
        final double height = Optional.ofNullable(dto.getHeight()).orElse(0.0);
        final double weight = Optional.ofNullable(dto.getWeight()).orElse(0.0);

        // BMI计算（含除零保护）
        final double bmi = height > 0 ? weight / Math.pow(height / 100, 2) : 0;
        final String bmiAlert = bmi >= 25 ? "🚩" : "";

        // 安全提示构建（新增）
        final String safetyNotice = buildSafetyNotice(dto.getBloodPressure(), dto.getMedicalHistory());

        // 模板参数（顺序严格校验）
        final Object[] templateParams = {
                dto.getElderlyName(),
                age,
                getAgeGeneration(age),
                height,
                weight,
                bmi,
                bmiAlert,
                Optional.ofNullable(dto.getBloodPressure()).orElse("未记录"),
                Optional.ofNullable(dto.getIsSmoking())
                        .map(v -> "是".equals(v) ? "吸烟 | " : "无").orElse("无"),
                Optional.ofNullable(dto.getIsDrinking())
                        .map(v -> "是".equals(v) ? "饮酒" : "无").orElse("无"),
                safetyNotice
        };

        return chatString(String.format(HEALTH_STRATEGY_PROMPT_TEMPLATE, templateParams));
    }

    // 新增安全提示构建方法
    private String buildSafetyNotice(String bloodPressure, String medicalHistory) {
        StringBuilder notice = new StringBuilder();

        // 血压提示
        if (bloodPressure != null && isNormalBloodPressure(bloodPressure)) {
            notice.append("⚠️ 运动前需测量血压，高于160/100mmHg暂停锻炼\n");
        } else {
            notice.append("⭕ 未获取有效血压数据，建议体检后更新方案\n");
        }

        // 病史提示
        Optional.ofNullable(medicalHistory)
                .filter(h -> !h.isEmpty())
                .ifPresent(h -> notice.append("‼️ 存在病史记录，建议咨询专科医生\n"));

        return notice.toString();
    }


    private String getAgeGeneration(Integer age) {
        if (age == null) return "未知";
        int birthYear = LocalDateTime.now().getYear() - age;
        if (birthYear >= 1940 && birthYear <= 1949) return "40后";
        if (birthYear >= 1950 && birthYear <= 1959) return "50后";
        if (birthYear >= 1960 && birthYear <= 1969) return "60后";
        if (birthYear >= 1970 && birthYear <= 1979) return "70后";
        if (birthYear >= 1980 && birthYear <= 1989) return "80后";
        if (birthYear >= 1990 && birthYear <= 1999) return "90后";
        return "其他";
    }

    // 新增血压评估方法
    private boolean isNormalBloodPressure(String bloodPressure) {
        try {
            String[] parts = bloodPressure.split("/");
            int systolic = Integer.parseInt(parts[0]);
            int diastolic = Integer.parseInt(parts[1]);
            return systolic < 140 && diastolic < 90;
        } catch (Exception e) {
            log.warn("血压数据格式异常: {}", bloodPressure);
            return false;
        }
    }

    public List<String> generateImage(ImageRequestDTO imageRequestDTO) {
        Integer n = 1;
        int width = 1024;
        int height = 1024;
        if (imageRequestDTO.getBatchSize() != null) {
            n = imageRequestDTO.getBatchSize();
        }
        if (imageRequestDTO.getImageSize() != null) {
            String[] size = imageRequestDTO.getImageSize().split("x");
            width = Integer.parseInt(size[0]);
            height = Integer.parseInt(size[1]);
        }
        ImageOptions options = ImageOptionsBuilder.builder()
                .N(n)
                .width(width)
                .height(height)
                .build();
        List<String> urls = new ArrayList<>();
        imageModel.call(new ImagePrompt(imageRequestDTO.getPrompt(), options))
                .getResults().forEach(result -> urls.add(result.getOutput().getUrl()));
        return urls;
    }
}
